package tools.clustering.isodata;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

import tools.clustering.isodata.workStrategies.DistanceHistogram;
import tools.utils.DoubleMatrix;

import neuro.uu.se.utils.Utils;

public class isodata_stat {
	static DoubleMatrix data;
	static ArrayList<Integer> name;
	public static void main(String[] args)throws Exception{
		consensusClusters(args[0], args[1], Double.parseDouble(args[2]));
//		intersectionClusters(args[0], args[1]);
//		consensusClusters("/local/out/Linn/Allaseq7tm2_many.csv", "/local/out/Linn/Allaseq7tm2_intersection.csv");
		
		System.out.println("Done!");
	}

//	public static void distanceHistogram(String inFile,double binSize,String outFile)throws Exception{
//		ObjectInputStream in =new ObjectInputStream(new FileInputStream(inFile));
//		isodata_datum datum=(isodata_datum)in.readObject();
//		in.close();
//		distanceHistogram(datum, binSize, outFile);
//	}
	public static void distanceHistogram(isodata_datum datum,double binSize,String outFile)throws Exception{
		Object[] keyset=datum.keySet().toArray();
		HashMap<Integer, Integer> results= new HashMap<Integer, Integer>();
		Integer tmp;
		for (int i = 0; i < keyset.length; i++) {
			for (int j = 0; j <i; j++) {
				tmp=new Integer((int)Math.floor(((DoubleMatrix)datum.getData((Integer)keyset[i])).euclidianLengthFromRowTo(0, ((DoubleMatrix)datum.getData((Integer)keyset[j])))/binSize));
				if (results.containsKey(tmp)) {
					results.put(tmp, new Integer(results.get(tmp)+1));
				}else{
					results.put(tmp, new Integer(1));
				}
			}
		}
		BufferedWriter out=new BufferedWriter(new FileWriter(outFile));
		for (Integer i : results.keySet()) {
			out.write((i*binSize)+"\t"+results.get(i)+"\n");
		}
		out.close();
	}
	public static void distanceHistogramThreaded(isodata_datum datum,double binSize,String outFile)throws Exception{
		ArrayList<ArrayList> list=new ArrayList<ArrayList>();
		Object[] keyset=datum.keySet().toArray();
		ArrayList tmp;
		Double bz=new Double(binSize);
		for (int i = 0; i < keyset.length; i++) {
			for (int j = 0; j <i; j++) {
				tmp=new ArrayList();
				tmp.add(datum.getData((Integer)keyset[i]));
				tmp.add(datum.getData((Integer)keyset[j]));
				tmp.add(bz);
				list.add(tmp);
			}
		}
		tmp=Utils.forEachListObject(new DistanceHistogram(), list);
		HashMap<Integer, Integer> results= new HashMap<Integer, Integer>();
		for (Object i0 : tmp) {
			if (results.containsKey((Integer)i0)) {
				results.put((Integer)i0, new Integer(results.get(i0)+1));
			}else{
				results.put((Integer)i0, new Integer(1));
			}
		}
		BufferedWriter out=new BufferedWriter(new FileWriter(outFile));
		for (Integer i : results.keySet()) {
			out.write((i*binSize)+"\t"+results.get(i)+"\n");
		}
		out.close();
	}
	public static void consensusClusters(String inFile,String outPrefix,double limit)throws Exception{
		System.out.println("Reading data...");
		try{
			ObjectInputStream in=new ObjectInputStream(new FileInputStream(inFile));
			name=(ArrayList<Integer>)in.readObject();
			data=(DoubleMatrix)in.readObject();
			in.close();
		}catch (Exception e) {
			BufferedReader in=new BufferedReader(new FileReader(inFile));
			in.readLine();//read the first line away
			name=new ArrayList<Integer>();
			ArrayList<String> tmp=new ArrayList<String>();
			for(String s=in.readLine();s!=null;s=in.readLine()){
				if (s.length()>0) {
					tmp.add(s);
					name.add(new Integer(s.split("\t")[0]));
				}
			}
//			System.out.println(name.size());
//			System.out.println(tmp.size());
			data=new DoubleMatrix(name.size(),tmp.get(0).split("\t").length-1);
			String[] l;
			for(int i=0;i<tmp.size();i++){
				l=tmp.get(i).split("\t");
				for (int j = 0; j < l.length-1; j++) {
					data.set(i, j, Double.parseDouble(l[j+1]));
				}
			}
			ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(outPrefix+"_intersection.data"));
			out.writeObject(name);
			out.writeObject(data);
			out.close();
		}
		consensusClusters(outPrefix,limit);
	}
	public static void consensusClusters(String outPrefix,double limit)throws Exception{
		if(name==null || data==null){
			throw new Exception("No data to work with");
		}else{
			//Find initial clusters
			System.out.println("Clustering clusters...");
			HashMap<Integer, ArrayList<Integer>> initialClusters=new HashMap<Integer, ArrayList<Integer>>();
			ArrayList<ArrayList<Integer>> finalClusters=new ArrayList<ArrayList<Integer>>();
			ArrayList<Integer> pos,tmp;
			Random rand=new Random();
			double max,cur;
			for(int i=0;i<data.getHeight();i++){
				pos=new ArrayList<Integer>();
				pos.add(0);
				max=data.get(i, 0);
				for(int j=1;j<data.getWidth();j++){
					cur=data.get(i, j);
					if (cur==max) {
						pos.add(j);
					} else if(cur>max){
						pos=new ArrayList<Integer>();
						pos.add(j);
						max=cur;
					}
				}
				for (Integer j : pos) {
					if (!initialClusters.containsKey(j)) {
						initialClusters.put(j, new ArrayList<Integer>());
					}
					initialClusters.get(j).add(i);
				}
			}
			boolean[] used=new boolean[data.getWidth()-1];
			boolean done=false;
			int i0=rand.nextInt(used.length);
			while(!done){
				//find a starting cluster
				while (used[i0]) {
					i0=rand.nextInt(used.length);
				}
				used[i0]=true;
				tmp=new ArrayList<Integer>();
				tmp.add(i0);
				addNeighbours(i0, limit, tmp);
				for (Integer i : tmp) {
					used[i]=true;
				}
				finalClusters.add(tmp);
				done=true;
				for (int i = 0; i < used.length; i++) {
					if(!used[i]){
						done=false;
						break;
					}
				}
			}
			System.out.println("Printing...");
			BufferedWriter Sout=new BufferedWriter(new FileWriter(outPrefix+"_clusters_data.csv"));
			BufferedWriter Cout=new BufferedWriter(new FileWriter(outPrefix+"_clusters_long.csv"));
			i0=0;
			for (ArrayList<Integer> list : finalClusters) {
				Cout.write("Cluster_"+i0+"\n");
				Cout.write("Containg clusters: ");
				for (Integer i : list) {
					Cout.write(i+",");
				}
				Cout.write("\nContaining IDs:");
				for (Integer i : list) {
//					System.out.println(""+i);
//					System.out.println(initialClusters.get(i).size());
					for (Integer j : initialClusters.get(i)) {
						Cout.write(name.get(j)+",");
						Sout.write(name.get(j)+"\t"+i0+"\n");
					}
				}
				Cout.write("\n");
				i0++;
			}
			Sout.close();
			Cout.close();
		}
	}
	private static void addNeighbours(int j0,double limit,ArrayList<Integer> tmp)throws Exception{
		for(int i=0;i<data.getHeight();i++){
			if(data.get(i, j0)>=limit){
				for(int j=0;j<data.getWidth()-1;j++){
					if(j!=j0){
						if(data.get(i, j)>=limit){
							if(!tmp.contains(new Integer(j))){
								tmp.add(j);
								addNeighbours(j, limit, tmp);
							}
						}
					}
				}
			}
		}
//		System.out.println(tmp.size());
	}
	/**
	 * 
	 * @param inFile _many.csv file from isodata.printResult().. first column contains id, second anything, and the rest is clusterIDs
	 * @param outFile
	 * @throws Exception
	 */
	public static void intersectionClusters(String inFile,String outFile)throws Exception{
		ArrayList<HashMap<Integer, Integer>> id_pos=new ArrayList<HashMap<Integer,Integer>>();
		ArrayList<HashMap<Integer, Integer>> pos_id=new ArrayList<HashMap<Integer,Integer>>();
		ArrayList<ArrayList<ArrayList<Integer>>> list=new ArrayList<ArrayList<ArrayList<Integer>>>();
		
		//Create datastructure
		System.out.println("Reading...");
		BufferedReader in=new BufferedReader(new FileReader(inFile));
		String[] l;
		int antal=0;
		Integer curT,id;
		for(String s=in.readLine();s!=null;s=in.readLine()){
			if (s.length()>0) {
				l=s.split("\t");
				if (list.size()==0) {
					for (int i = 0; i < l.length-2; i++) {
						id_pos.add(new HashMap<Integer, Integer>());
						pos_id.add(new HashMap<Integer, Integer>());
						list.add(new ArrayList<ArrayList<Integer>>());
					}
				}
				curT=new Integer(l[0]);
				for (int i = 0; i < l.length-2; i++) {
					id=new Integer(0);
					try{
					id=new Integer(l[i+2]);
					}catch (Exception e) {
						System.out.println(s);
						System.exit(616);
					}
					if (id_pos.get(i).containsKey(id)) {
						list.get(i).get(id_pos.get(i).get(id)).add(curT);
					}else{
						id_pos.get(i).put(id, list.get(i).size());
						pos_id.get(i).put(list.get(i).size(), id);
						list.get(i).add(new ArrayList<Integer>());
						list.get(i).get(id_pos.get(i).get(id)).add(curT);
					}
				}
			}
		}
		in.close();
		
		//How to match?
		//Extract Intersections
		System.out.println("Finding intersection...");
		ArrayList<ArrayList<Integer>> intersection=totalIntersection(list);
//		System.out.println(intersection.size());
//		BufferedWriter out=new BufferedWriter(new FileWriter(outFile));
//		for (ArrayList<Integer> i : totalIntersection(list)) {
//			if(i.size()>1){
//				for (Integer j : i) {
//					out.write(j+"\t");
//				}
//				out.write("\n");
//			}
//		}
//		out.close();
		//Let the intersection define standard clusterNr
		System.out.println("Defining clusters...");
		HashMap<Integer, HashMap<Integer, Integer>> result=new HashMap<Integer, HashMap<Integer, Integer>>();
		int clusterNr;
		boolean found;
		for (ArrayList<ArrayList<Integer>> i : list) {
			clusterNr=0;
			for (ArrayList<Integer> j : i) {
				found=false;
				for(int k=0;k<intersection.size();k++){
					if (j.contains(intersection.get(k).get(0))) {
						clusterNr=k;
						found=true;
						for (Integer m : j) {
							if (!result.containsKey(m)) {
								result.put(m, new HashMap<Integer, Integer>());
								result.get(m).put(clusterNr, 1);
							}else if (!result.get(m).containsKey(clusterNr)) {
								result.get(m).put(clusterNr, 1);
							}else{
//								System.out.println(result.get(k).get(clusterNr).intValue()+1);
								result.get(m).put(clusterNr, result.get(m).get(clusterNr).intValue()+1);
							}
						}
					}
				}
				if(!found){
//					System.out.println("no cluster");
					clusterNr=-616;
					for (Integer k : j) {
						if (!result.containsKey(k)) {
							result.put(k, new HashMap<Integer, Integer>());
							result.get(k).put(clusterNr, 1);
						}else if (!result.get(k).containsKey(clusterNr)) {
							result.get(k).put(clusterNr, 1);
						}else{
//							System.out.println(result.get(k).get(clusterNr).intValue()+1);
							result.get(k).put(clusterNr, result.get(k).get(clusterNr).intValue()+1);
						}
					}
				}
//				System.out.println(clusterNr);
//				for (Integer k : j) {
//					if (!result.containsKey(k)) {
//						result.put(k, new HashMap<Integer, Integer>());
//						result.get(k).put(clusterNr, 1);
//					}else if (!result.get(k).containsKey(clusterNr)) {
//						result.get(k).put(clusterNr, 1);
//					}else{
////						System.out.println(result.get(k).get(clusterNr).intValue()+1);
//						result.get(k).put(clusterNr, result.get(k).get(clusterNr).intValue()+1);
//					}
//				}
			}
		}
//		System.out.println(antal);
//		System.out.println("Number of clusters: "+result.size());
		System.out.println("printing...");
		BufferedWriter out=new BufferedWriter(new FileWriter(outFile+".csv"));
//		System.out.print("id");
		out.write("id");
		for (int i=0;i<intersection.size();i++){
//			System.out.print("\tcluster"+i);
			out.write("\tcluster"+i);
		}
//		System.out.println("\tNo cluster");
		out.write("\tNo cluster\n");
		Set<Integer> test=result.keySet();
		
		Object[] keyset=result.keySet().toArray();
		System.err.println(keyset.length);
		data=new DoubleMatrix(keyset.length,intersection.size()+1);
		name=new ArrayList<Integer>();
		int nr;
		for (int tmp=0;tmp<keyset.length;tmp++) {
			Integer i=(Integer) keyset[tmp];
			name.add(i);
//			System.out.print(i.intValue());
			out.write(i.intValue()+"");
			for(int j=0;j<intersection.size();j++){
				if (result.get(i).containsKey(j)) {
//					System.out.print("\t"+result.get(i).get(j).intValue());
					nr=result.get(i).get(j).intValue();
					out.write("\t"+nr);
					data.add(tmp, j, nr);
				}else{
//					System.out.print("\t0");
					out.write("\t0");
					data.add(tmp, j, 0);
				}
			}
			if (result.get(i).containsKey(-616)) {
//				System.out.println("\t"+result.get(i).get(new Integer(-616)).intValue());
				nr=result.get(i).get(new Integer(-616)).intValue();
				out.write("\t"+nr+"\n");
				data.add(tmp, intersection.size(), nr);
			}else{
//				System.out.println("\t0");
				out.write("\t0\n");
				data.add(tmp, intersection.size(), 0);
			}
		}
		out.close();
		//write data to object
		ObjectOutputStream oOut=new ObjectOutputStream(new FileOutputStream(outFile+".data"));
		oOut.writeObject(name);
		oOut.writeObject(data);
		oOut.close();
	}
	//needs to be limited to a subset?
	private static ArrayList<ArrayList<Integer>> totalIntersection(ArrayList<ArrayList<ArrayList<Integer>>> set){
		if (set.size()==1) {
			return set.get(0);
		}
		ArrayList<ArrayList<ArrayList<Integer>>> subset=new ArrayList<ArrayList<ArrayList<Integer>>>();
		for(int i=1;i<set.size();i++){
			subset.add(intersection(set.get(0), set.get(i)));
		}
		return totalIntersection(subset);
	}
	private static ArrayList<ArrayList<Integer>> intersection(ArrayList<ArrayList<Integer>> a,ArrayList<ArrayList<Integer>> b){
		ArrayList<ArrayList<Integer>> ans=new ArrayList<ArrayList<Integer>>();
		ArrayList<Integer> tmp;
		for (ArrayList<Integer> i : a) {
			for (ArrayList<Integer> j : b) {
				tmp=new ArrayList<Integer>();
				for (Integer k : i) {
					if (j.contains(k)) {
						tmp.add(k);
					}
				}
				if (tmp.size()>1) {
					ans.add(tmp);
				}
			}
		}
		return ans;
	}
}
